
#include <gst/gst.h>
#include <glib.h>
#include <math.h>
#include <gstreamer-1.0/gst/app/app.h>
#include <stdlib.h>
#include <string.h>
#include <gstreamer-0.10/gst/gstelement.h>

struct _tux_gst_pipeline{
    GstElement *pipeline, *source, *audioconvert, *audioresample, *level, *sink;
};

typedef struct _tux_gst_pipeline tux_gst_pipeline;


static gboolean
message_handler (GstBus * bus, GstMessage * message, gpointer data)
{
    
  if (message->type == GST_MESSAGE_ELEMENT) {
    const GstStructure *s = gst_message_get_structure (message);
    const gchar *name = gst_structure_get_name (s);

    if (strcmp (name, "level") == 0) {
      gint channels;
      GstClockTime endtime;
      gdouble rms_dB, peak_dB, decay_dB;
      gdouble rms;
      const GValue *array_val;
      const GValue *value;
      GValueArray *rms_arr, *peak_arr, *decay_arr;
      gint i;

      if (!gst_structure_get_clock_time (s, "endtime", &endtime))
        g_warning ("Could not parse endtime");

      /* the values are packed into GValueArrays with the value per channel */
      array_val = gst_structure_get_value (s, "rms");
      rms_arr = (GValueArray *) g_value_get_boxed (array_val);

      array_val = gst_structure_get_value (s, "peak");
      peak_arr = (GValueArray *) g_value_get_boxed (array_val);

      array_val = gst_structure_get_value (s, "decay");
      decay_arr = (GValueArray *) g_value_get_boxed (array_val);

      /* we can get the number of channels as the length of any of the value
       * arrays */
      channels = rms_arr->n_values;
      g_print ("endtime: %" GST_TIME_FORMAT ", channels: %d\n",
          GST_TIME_ARGS (endtime), channels);
      for (i = 0; i < channels; ++i) {

        g_print ("channel %d\n", i);
        //value = g_value_array_get_nth (rms_arr, i);
        value = rms_arr->values +i; 
        rms_dB = g_value_get_double (value);

        //value = g_value_array_get_nth (peak_arr, i);
        value = peak_arr->values + i;
        peak_dB = g_value_get_double (value);

        //value = g_value_array_get_nth (decay_arr, i);
        value = decay_arr->values + i;
        decay_dB = g_value_get_double (value);
        g_print ("    RMS: %f dB, peak: %f dB, decay: %f dB\n",
            rms_dB, peak_dB, decay_dB);

        /* converting from dB to normal gives us a value between 0.0 and 1.0 */
        rms = pow (10, rms_dB / 20);
        g_print ("    normalized rms value: %f\n", rms);
      }
    }
  }
  /* we handled the message we want, and ignored the ones we didn't want.
   * so the core can unref the message for us */
  return TRUE;
}


static gboolean tux_gst_pipeline_set_source_state_ready(tux_gst_pipeline *pipeline) {
  if (gst_element_set_state (pipeline->source, GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) {
      g_critical("could not set state of source to ready");
      return FALSE;
  }    
  return TRUE;
}

gboolean tux_gst_pipeline_set_state_playing(tux_gst_pipeline *pipeline) {
  if (gst_element_set_state (pipeline->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
      g_critical("could not set state of pipeline to playing");
      return FALSE;
  }  
  return TRUE;
}

gboolean tux_gst_pipeline_set_state_paused(tux_gst_pipeline *pipeline) {
 if (gst_element_set_state (pipeline->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
      g_critical("could not set state of pipeline to paused");
      return FALSE;
  }  
  return TRUE;    
}

static gboolean tux_gst_pipeline_set_state_null(tux_gst_pipeline *pipeline) {
 if (gst_element_set_state (pipeline->pipeline, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) {
      g_critical("could not set state of pipeline to null");
      return FALSE;
  }  
  return TRUE;    
    
}

static GstElement *tux_gst_create_app_sink_with_default_values(GstFlowReturn new_sample_callback(GstElement *, gpointer data), gpointer data) {
  GstCaps *appsink_caps;
  GstElement *app_sink;
          
  app_sink = gst_element_factory_make ("appsink", NULL);
  if (!app_sink) {
      g_critical("could not create app sink");
      return NULL;
  }
  appsink_caps = gst_caps_new_simple ("audio/x-raw", "format", G_TYPE_STRING, "S8", "rate", G_TYPE_INT, 44100, "channels", G_TYPE_INT, 1, NULL);
  if (!appsink_caps) {
      g_critical("could not create caps for app sink");
      gst_object_unref(app_sink);
      return NULL;      
   }
   gst_app_sink_set_caps(GST_APP_SINK(app_sink), appsink_caps);
   gst_app_sink_set_emit_signals(GST_APP_SINK(app_sink),TRUE);
   g_signal_connect(app_sink, "new-sample", G_CALLBACK(new_sample_callback),data);
   return app_sink;
}

static GstElement *tux_gst_create_level_with_default_values() {
  GstElement *level = gst_element_factory_make("level",NULL);
  if (!level) {
      g_critical("could not create gst-level");
      return NULL;
  }
  g_object_set (G_OBJECT (level), "post-messages", TRUE, NULL);
  return level;  
}

static GstElement *tux_gst_create_audio_resample_with_default_values() {
    GstElement *audio_resample = gst_element_factory_make("audioresample",NULL);
    if (!audio_resample) {
        g_critical("could not create gst-audioresample");
        return NULL;
    }
    return audio_resample;
}

static GstElement *tux_gst_create_audio_convert_with_default_values() {
    GstElement *audio_convert = gst_element_factory_make("audioconvert",NULL);
    if (!audio_convert) {
        g_critical("could not create gst-audioconvert");
        return NULL;
    }
    return audio_convert;
}

static GstElement *tux_gst_create_alsa_src_with_default_values(char *device) {
    GstElement *alsa_src = gst_element_factory_make ("alsasrc",  NULL);
    if (!alsa_src) {
        g_critical("could not create gst-alsasrc");
        return NULL;
    }
    g_object_set (G_OBJECT(alsa_src),"device",device,NULL);
    return alsa_src;
}

static GstElement *tux_gst_create_pulse_src_with_default_values() {
    GstElement *pulse_src = gst_element_factory_make("pulsesrc",NULL);
    if (!pulse_src) {
        g_critical("could not create gst-pulsesrc");
        return NULL;
    }
    return pulse_src;  
}

static GstElement *tux_gst_create_jack_src_with_default_values() {
    GstElement *jack_src = gst_element_factory_make("jackaudiosrc",NULL);
    if (!jack_src) {
        g_critical("could not create gst-jackaudiosrc");
        return NULL;
    }
    return jack_src;
}

static GstElement *tux_gst_create_pipeline_with_default_values(gboolean bus_call (GstBus *, GstMessage *, gpointer), gpointer data) {
    GstBus *bus;
    guint bus_watch_id;
    GstElement *pipeline = gst_pipeline_new(NULL);
    if (!pipeline) {
        g_critical("could not create gst-pipeline");
        return NULL;
    }
  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  bus_watch_id = gst_bus_add_watch (bus, bus_call, data);
  gst_object_unref (bus);    
    return pipeline;
}

void tux_gst_pipeline_free(tux_gst_pipeline *pipeline) {
    if (pipeline) {
        tux_gst_pipeline_set_state_null(pipeline);
        if (pipeline->source) {
            gst_element_unlink_many(pipeline->source,pipeline->audioconvert,pipeline->audioresample,pipeline->level,pipeline->sink,NULL);
            gst_bin_remove_many(GST_BIN(pipeline->pipeline),pipeline->source,pipeline->audioconvert,pipeline->audioresample,pipeline->level,pipeline->sink,NULL);
        } else {
            gst_element_unlink_many(pipeline->audioconvert,pipeline->audioresample,pipeline->level,pipeline->sink,NULL);
            gst_bin_remove_many(GST_BIN(pipeline->pipeline),pipeline->audioconvert,pipeline->audioresample,pipeline->level,pipeline->sink,NULL);
        }
        g_free(pipeline);
    }   
}

tux_gst_pipeline *tux_gst_create_app_pipeline_without_source(gboolean bus_call (GstBus *, GstMessage *, gpointer),GstFlowReturn new_sample_callback(GstElement *, gpointer data),gpointer data) {
    tux_gst_pipeline *pipeline = g_new0(tux_gst_pipeline, 1);
    pipeline->pipeline=tux_gst_create_pipeline_with_default_values(bus_call, data);
    pipeline->audioconvert=tux_gst_create_audio_convert_with_default_values();
    pipeline->audioresample=tux_gst_create_audio_resample_with_default_values();
    pipeline->level=tux_gst_create_level_with_default_values();
    pipeline->sink=tux_gst_create_app_sink_with_default_values(new_sample_callback, data);
    if (!pipeline->audioconvert || !pipeline->audioresample || !pipeline->level || !pipeline->sink || !pipeline->pipeline) {
        g_critical("could not creat tux_gst_pipeline");
        tux_gst_pipeline_free(pipeline);
        return NULL;
    }
    
    gst_bin_add_many(GST_BIN(pipeline->pipeline),pipeline->audioconvert,pipeline->audioresample,pipeline->level,pipeline->sink,NULL);
    gst_element_link_many(pipeline->audioconvert,pipeline->audioresample,pipeline->level,pipeline->sink,NULL);
    return pipeline;
}

gboolean tux_gst_pipeline_remove_source(tux_gst_pipeline *pipeline) {
    if (!pipeline->source) {
        g_warning("tux_gst_pipeline does not contain a valid source to remove");
        return FALSE;
    }
    gst_element_unlink(pipeline->source,pipeline->audioconvert);
    if (!gst_bin_remove(GST_BIN(pipeline->pipeline),pipeline->source)) {
        g_critical("could not remove source from pipeline");
        return FALSE;
    }
    pipeline->source=NULL;
    return TRUE;
}

gboolean tux_gst_pipeline_add_source(char *source, char *device, tux_gst_pipeline *pipeline) {
    if (strcmp(source,"alsa") == 0) {
            pipeline->source = tux_gst_create_alsa_src_with_default_values(device);
    } else if (strcmp(source, "jack") == 0) {
            pipeline->source = tux_gst_create_jack_src_with_default_values();
    } else if (strcmp(source, "pulse") == 0) {
            pipeline->source = tux_gst_create_pulse_src_with_default_values();
    } else {
            g_critical("unknown source %s",source);
            return FALSE;
    }
    if (!pipeline->source) {
        g_critical("could not add pipeline source %s",source);
        return FALSE;
    } 
    if (!gst_bin_add(GST_BIN(pipeline->pipeline),pipeline->source)) {
        g_critical("could not add source to bin");
        gst_object_unref(pipeline->source);
        return FALSE;
    }
    if (!gst_element_link(pipeline->source,pipeline->audioconvert)) {
        g_critical("could not link source to audioconvert");
        gst_bin_remove(GST_BIN(pipeline->pipeline),pipeline->source);
        gst_object_unref(pipeline->source);
        return FALSE;
    }
    if (!tux_gst_pipeline_set_source_state_ready(pipeline)) {
        tux_gst_pipeline_remove_source(pipeline);
        return FALSE;
    }
    return TRUE;
}


gboolean tux_gst_pipeline_replace_source(char *source, char *device, tux_gst_pipeline *pipeline) {
    tux_gst_pipeline_remove_source(pipeline);
    return tux_gst_pipeline_add_source(source,device,pipeline);    
}


tux_gst_pipeline *tux_gst_create_app_pipeline(char *source, char *device, gboolean bus_call (GstBus *, GstMessage *, gpointer),GstFlowReturn new_sample_callback(GstElement *, gpointer), gpointer data) {
    tux_gst_pipeline *pipeline = tux_gst_create_app_pipeline_without_source(bus_call,new_sample_callback, data);
    if (!pipeline) {
        g_critical("could not create pipeline");
        return NULL;
    }
    if (!tux_gst_pipeline_add_source(source,device,pipeline)) {
        g_critical("could not create source for app pipeline");
        tux_gst_pipeline_free(pipeline);
        return NULL;
    }
    return pipeline;
}

gboolean tux_gst_pipeline_has_source(tux_gst_pipeline *pipeline) {
    return (pipeline->source != NULL);
}   



void create_loop() {
  GMainLoop *loop;
  loop = g_main_loop_new (NULL, FALSE);
    
}

void create_loop2()
{
  GMainLoop *loop;

  GstElement *pipeline, *source, *audioconvert, *audioresample,*capsfilter, *level, *sink;
  GstBus *bus;
  guint bus_watch_id;
  GstCaps *appsink_caps;

  loop = g_main_loop_new (NULL, FALSE);


  pipeline = gst_pipeline_new ("audio-player");
  source   = gst_element_factory_make ("alsasrc",  NULL);
  audioconvert = gst_element_factory_make("audioconvert",NULL);
  audioresample = gst_element_factory_make("audioresample",NULL);
  level = gst_element_factory_make("level",NULL);
  sink     = gst_element_factory_make ("appsink", NULL);
  
  if (!pipeline || !source  || !audioconvert || !audioresample || !sink ) {
    g_printerr ("One element could not be created. Exiting.\n");
    return;
  }
  
 appsink_caps = gst_caps_new_simple ("audio/x-raw", "format", G_TYPE_STRING, "S8", "rate", G_TYPE_INT, 44100, "channels", G_TYPE_INT, 1, NULL);
 
 if (appsink_caps == NULL) {
     g_critical("could not create caps");
 }
 
 gst_app_sink_set_caps(GST_APP_SINK(sink), appsink_caps);
 gst_app_sink_set_emit_signals(GST_APP_SINK(sink),TRUE);
 //g_signal_connect(sink, "new-sample", G_CALLBACK(new_sample),NULL);
  
 //GstPad *audioconvert_pad=gst_element_get_static_pad(audioconvert,"src");
  
  g_object_set (G_OBJECT(source),"device","hw:3,0",NULL);
  g_object_set (G_OBJECT (level), "post-messages", TRUE, NULL);
  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  //bus_watch_id = gst_bus_add_watch (bus, bus_call, NULL);
  //gst_object_unref (bus);

  gst_bin_add_many(GST_BIN(pipeline),source,audioconvert,audioresample,sink,NULL);
  gst_element_link_many(source,audioconvert,audioresample,sink,NULL);
  if (gst_element_set_state (source, GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) {
      g_critical("could not set state of source to ready");
  }
  if (gst_element_set_state (pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
      g_critical("could not set state of pipeline to playing");
  }

  g_print ("Running...\n");
  g_main_loop_run(loop);

  g_print ("Returned, stopping playback\n");
  gst_element_set_state (pipeline, GST_STATE_NULL);

  g_print ("Deleting pipeline\n");
  gst_object_unref (GST_OBJECT (pipeline));
  g_source_remove (bus_watch_id);
  g_main_loop_unref (loop);

}
